/*
 * luabase.c
 *
 * Copyright (C) 2011 - Dr.NP
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @version luabase.c 1.0.0
 * @package exts/luabase
 * @author Dr.NP <np@bsgroup.org>
 * @update 05/30/2011
 */

/**
 * Standard runtime extension
 * BSP.Package and LUA support
 *
 * === CHANGELOG ===
 * [05/30/2011] - Creation
 */

#include "bsp.h"
#include "bsp_app.h"
#include "bsp_script.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>

#include "luabase.h"
#include "luabase_api.h"

void luabase_on_load(int);
void luabase_on_unload(int);
void luabase_on_conn(int);
void luabase_on_close(int);
size_t luabase_on_data(int, char *, size_t);
void luabase_on_error(int);

void ins_table(lua_State *s, struct abuff_t *);

struct bsp_ext_entry_t entry = {
    luabase_on_load, 
    luabase_on_unload, 
    luabase_on_conn, 
    luabase_on_close, 
    luabase_on_data, 
    luabase_on_error
};

struct bsp_ext_api_t api[] = {
    {luabase_api_send, "bsp_send"}, 
    {luabase_api_echotest, "bsp_echo"}, 
    {NULL, NULL}
};

BSP_EXTENSION bsp_luabase = {
    0, 
    "luabase", 
    &entry, 
    api
};

void luabase_on_load(int app_id)
{
    struct app_t *a = get_app(app_id);
    struct script_t *s = a ? get_script(a->script_id) : NULL;

    if (s)
    {
        if (1 == s->load_times)
        {
            // First load
            lua_getglobal(s->main_state, LUABASE_FUNC_ON_LOAD);
            if (lua_pcall(s->main_state, 0, 0, 0))
            {
                debug_info("LUA state : Script <%s> has no available function %s\n", s->script_filename, LUABASE_FUNC_ON_LOAD);
            }
        }

        else
        {
            lua_getglobal(s->main_state, LUABASE_FUNC_ON_RELOAD);
            if (lua_pcall(s->main_state, 0, 0, 0))
            {
                debug_info("LUA state : Script <%s> has no available function %s\n", s->script_filename, LUABASE_FUNC_ON_RELOAD);
            }
        }
    }
    
    return;
}

void luabase_on_unload(int app_id)
{
    struct app_t *a = get_app(app_id);
    struct script_t *s = a ? get_script(a->script_id) : NULL;

    if (s)
    {
        lua_getglobal(s->main_state, LUABASE_FUNC_ON_UNLOAD);
        if (lua_pcall(s->main_state, 0, 0, 0))
        {
            debug_info("LUA state : Script <%s> has no available function %s\n", s->script_filename, LUABASE_FUNC_ON_UNLOAD);
        }
    }
    
    return;
}

void luabase_on_conn(int clt_id)
{
    struct client_t *c = get_client(clt_id);
    struct server_t *srv = c ? get_server(c->srv_id) : NULL;
    struct app_t *a = srv ? get_app(srv->app_id) : NULL;
    struct script_t *s = a ? get_script(a->script_id) : NULL;
    lua_State *ls = s ? s->worker_states[c->worker_id] : NULL;
    
    if (s && ls)
    {
        lua_getglobal(ls, LUABASE_FUNC_ON_CONN);
        lua_pushinteger(ls, clt_id);
        lua_pcall(ls, 1, 0, 0);
    }
    
    return;
}

void luabase_on_close(int clt_id)
{
    int r;
    struct client_t *c = get_client(clt_id);
    struct server_t *srv = c ? get_server(c->srv_id) : NULL;
    struct app_t *a = srv ? get_app(srv->app_id) : NULL;
    struct script_t *s = a ? get_script(a->script_id) : NULL;
    lua_State *ls = s ? s->worker_states[c->worker_id] : NULL;
    
    if (s && ls)
    {
        lua_getglobal(ls, LUABASE_FUNC_ON_CLOSE);
        lua_pushinteger(ls, clt_id);
        r = lua_pcall(ls, 1, 0, 0);

        if (r != 0)
        {
            debug_info("Lua call error : %s\n", lua_tostring(ls, -1));
        }
    }
    
    return;
}

size_t luabase_on_data(int clt_id, char *data, size_t len)
{
    size_t ret = 0, remaining = 0;
    u_int32_t cmd_id;
    u_int32_t body_len;
    static char response_hb[8] = {0xFF, 0XFF, 0xFF, 0xFF, 0x0, 0x0, 0x0, 0x0};
    int res;
    
    struct client_t *c = get_client(clt_id);
    struct server_t *srv = c ? get_server(c->srv_id) : NULL;
    struct app_t *a = srv ? get_app(srv->app_id) : NULL;
    struct script_t *s = a ? get_script(a->script_id) : NULL;
    lua_State *ls = s ? s->worker_states[c->worker_id] : NULL;

    if (s && ls)
    {
        // Try unpack
        remaining = len;
        while (remaining >= 8)
        {
            cmd_id = get_u_int(data + ret);
            body_len = get_u_int(data + ret + 4);

            if (body_len > LUABASE_MAX_CMDBODY)
            {
                // Memory protection, all clear!!!
                return len;
            }

            if (remaining - 8 < body_len)
            {
                // Half package
                break;
            }

            if (body_len > 0)
            {
                lua_getglobal(ls, LUABASE_FUNC_ON_DATA);
                lua_pushinteger(ls, clt_id);
                lua_pushinteger(ls, cmd_id);
                luabase_bsp_unpack(ls, data + ret + 8, body_len);
                res = lua_pcall(ls, 3, 1, 0);
                if (0 != res)
                {
                    debug_info("Lua error : %s\n", lua_tostring(ls, -1));
                }

                lua_pop(ls, 1);
                ret += body_len + 8;
            }

            else
            {
                // Special command here
                if (cmd_id == LUABASE_CMD_HEARTBEAT)
                {
                    // Send response
                    append_client(clt_id, response_hb, 8);
                }
                
                ret += 8;
            }

            remaining -= (body_len + 8);
        }
    }
    
    return ret;
}

void luabase_on_error(int clt_id)
{
    struct client_t *c = get_client(clt_id);
    struct server_t *srv = c ? get_server(c->srv_id) : NULL;
    struct app_t *a = srv ? get_app(srv->app_id) : NULL;
    struct script_t *s = a ? get_script(a->script_id) : NULL;
    lua_State *ls = s ? s->worker_states[c->worker_id] : NULL;
    
    if (s && ls)
    {
        lua_getglobal(ls, LUABASE_FUNC_ON_ERROR);
        lua_pushinteger(ls, clt_id);
        lua_pcall(ls, 1, 0, 0);
    }
    
    return;
}

int luabase_bsp_unpack(lua_State *s, char *data, size_t len)
{
    int remaining = len, pos = 0;
    u_int8_t type;
    int array_layer;
    int kv;

    u_int8_t v_byte;
    u_int16_t v_short;
    u_int32_t v_int;
    u_int64_t v_longlong;
    double v_float;
    char *v_string;
    size_t str_len;
    
    if (!s)
    {
        return 0;
    }

    kv = array_layer = 0;
    lua_newtable(s);
    while (remaining > 0)
    {
        type = (unsigned char) data[pos];

        switch (type)
        {
            case PKG_VALUE_TYPE_BYTE : 
                pos ++;
                if (remaining < 2) break;
                
                v_byte = (unsigned char) data[pos];
                lua_pushinteger(s, v_byte);
                pos ++;
                
                break;

            case PKG_VALUE_TYPE_SHORT : 
                pos ++;
                if (remaining < 3) break;

                v_short = get_u_short(data + pos);
                lua_pushinteger(s, v_short);
                pos += 2;
                
                break;

            case PKG_VALUE_TYPE_INT : 
                pos ++;
                if (remaining < 5) break;

                v_int = get_u_int(data + pos);
                lua_pushinteger(s, v_int);
                pos += 4;
                
                break;

            case PKG_VALUE_TYPE_LONGLONG : 
                pos ++;
                if (remaining < 9) break;

                v_longlong = get_u_ll(data + pos);
                lua_pushnumber(s, v_longlong);
                pos += 8;
                
                break;

            case PKG_VALUE_TYPE_FLOAT : 
                pos ++;
                if (remaining < 9) break;

                memcpy(&v_float, data + pos, 8);
                lua_pushnumber(s, v_float);
                pos += 8;
                
                break;

            case PKG_VALUE_TYPE_STRING : 
                pos ++;
                str_len = get_u_int(data + pos);
                if (remaining < 5 + str_len) break;

                lua_pushlstring(s, data + pos + 4, str_len);
                pos += 4 + str_len;
                
                break;

            case PKG_VALUE_TYPE_ARRAY_START : 
                if (1 == kv)
                {
                    lua_newtable(s);
                    array_layer ++;
                    kv = 2;
                }
                pos += 1;
                
                break;

            case PKG_VALUE_TYPE_ARRAY_END : 
                if (0 == kv && array_layer > 0)
                {
                    lua_settable(s, -3);
                    array_layer --;
                    kv = 2;
                }
                pos += 1;

                break;

            case PKG_VALUE_TYPE_NULL : 
                lua_pushnil(s);
                pos += 1;
                
                break;

            default : 
                // Unsupported type
                if (0 == kv)
                {
                    // Write a special key
                    lua_pushstring(s, PKG_UNSUPPORTED_TYPE_KEY);
                }

                else
                {
                    lua_pushnil(s);
                }
                
                pos += 1;
                
                break;
        }
        //debug_info("Current layer : %d %d\n", kv, array_layer);
        //dump_stack(s);

        if (1 == kv)
        {
            lua_settable(s, -3);
            kv = 0;
        }

        else if (2 == kv)
        {
            kv = 0;
        }

        else
        {
            kv = 1;
        }

        //debug_info("Current layer : %d\n", kv);
        //dump_stack(s);
        
        remaining = len - pos;
    }

    if (1 == kv)
    {
        lua_pushnil(s);
        lua_settable(s, -3);
    }

    while (array_layer > 0)
    {
        lua_settable(s, -3);
        array_layer --;
    }

    return pos;
}

int luabase_bsp_pack(lua_State *s, struct abuff_t *b)
{
    if (!s || !b)
    {
        return RTN_ERROR_GENERAL;
    }

    if (!lua_istable(s, -1))
    {
        debug_info("Luabase::pack need a table for input\n");

        return RTN_ERROR_GENERAL;
    }

    ins_table(s, b);

    return RTN_SUCCESS;
}

void ins_table(lua_State *s, struct abuff_t *b)
{
    static unsigned char type[1], n_data[8];
    
    u_int8_t v_byte;
    u_int16_t v_short;
    u_int32_t v_int;
    u_int64_t v_longlong;
    double v_float;
    char *v_string;
    size_t str_len, n_len;

    if (!lua_istable(s, -1))
    {
        return;
    }

    lua_pushnil(s);
    while (lua_next(s, -2))
    {
        // For key
        if (lua_isnumber(s, -2))
        {
            v_longlong = lua_tointeger(s, -2);
            v_float = lua_tonumber(s, -2);

            if (v_float == (lua_Number) v_longlong)
            {
                // Integer
                if (v_longlong <= 0xFF)
                {
                    type[0] = PKG_VALUE_TYPE_BYTE;
                    n_data[0] = (unsigned char) v_longlong;
                    n_len = 1;
                }

                else if (v_longlong <= 0xFFFF)
                {
                    type[0] = PKG_VALUE_TYPE_SHORT;
                    put_u_short(n_data, (u_int16_t) v_longlong);
                    n_len = 2;
                }

                else if (v_longlong <= 0xFFFFFFFF)
                {
                    type[0] = PKG_VALUE_TYPE_INT;
                    put_u_int(n_data, (u_int32_t) v_longlong);
                    n_len = 4;
                }

                else
                {
                    // Too big value -_-
                    type[0] = PKG_VALUE_TYPE_LONGLONG;
                    put_u_ll(n_data, v_longlong);
                    n_len = 8;
                }
            }

            else
            {
                // Float
                type[0] = PKG_VALUE_TYPE_FLOAT;
                memcpy(n_data, &v_float, 8);
                n_len = 8;
            }

            append_abuff(b, type, 1, 1);
            append_abuff(b, n_data, n_len, 1);
        }

        else if (lua_isstring(s, -2))
        {
            v_string = (char *) lua_tolstring(s, -2, &str_len);

            type[0] = PKG_VALUE_TYPE_STRING;
            put_u_int(n_data, str_len);

            append_abuff(b, type, 1, 1);
            append_abuff(b, n_data, 4, 1);
            append_abuff(b, v_string, str_len, 1);
        }

        else
        {
            // Unsupported key type, do nothing
        }

        // For value
        if (lua_isnumber(s, -1))
        {
            v_longlong = lua_tointeger(s, -1);
            v_float = lua_tonumber(s, -1);

            if (v_float == (lua_Number) v_longlong)
            {
                // Integer
                if (v_longlong <= 0xFF)
                {
                    type[0] = PKG_VALUE_TYPE_BYTE;
                    n_data[0] = (unsigned char) v_longlong;
                    n_len = 1;
                }

                else if (v_longlong <= 0xFFFF)
                {
                    type[0] = PKG_VALUE_TYPE_SHORT;
                    put_u_short(n_data, (u_int16_t) v_longlong);
                    n_len = 2;
                }

                else if (v_longlong <= 0xFFFFFFFF)
                {
                    type[0] = PKG_VALUE_TYPE_INT;
                    put_u_int(n_data, (u_int32_t) v_longlong);
                    n_len = 4;
                }

                else
                {
                    // Too big value -_-
                    type[0] = PKG_VALUE_TYPE_LONGLONG;
                    put_u_ll(n_data, v_longlong);
                    n_len = 8;
                }
            }

            else
            {
                // Float
                type[0] = PKG_VALUE_TYPE_FLOAT;
                memcpy(n_data, &v_float, 8);
                n_len = 8;
            }

            append_abuff(b, type, 1, 1);
            append_abuff(b, n_data, n_len, 1);
        }

        else if (lua_isstring(s, -1))
        {
            v_string = (char *) lua_tolstring(s, -1, &str_len);

            type[0] = PKG_VALUE_TYPE_STRING;
            put_u_int(n_data, str_len);

            append_abuff(b, type, 1, 1);
            append_abuff(b, n_data, 4, 1);
            append_abuff(b, v_string, str_len, 1);
        }

        else if (lua_istable(s, -1))
        {
            type[0] = PKG_VALUE_TYPE_ARRAY_START;
            append_abuff(b, type, 1, 1);

            ins_table(s, b);

            type[0] = PKG_VALUE_TYPE_ARRAY_END;
            append_abuff(b, type, 1, 1);
        }

        else
        {
            // Unsupported value type, do nothing
        }

        lua_pop(s, 1);
    }

    return;
}
